//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
//---------------------------------------------------------------------------
#include <io.h>
#include <dir.h>
#include <filectrl.hpp>

#include "eic.h"
#include "CTarBz2.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
//---------------------------------------------------------------------------
#define LIBBZ2         "libbz2.dll"
#define USERDATAS_FILE "@@@@@@@@.EIC"
#define FLUSH_RUN      0
#define FLUSH_FINISH   1
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Exception BZ2
//---------------------------------------------------------------------------
__fastcall BZ2Exception::BZ2Exception( int nError) : Exception( EmptyStr)
{
  switch( m_nError = nError) {
    case BZ_CONFIG_ERROR       : Message = "Problème de configuration, vérifier les options de compilation de la librairie."; break;
    case BZ_IO_ERROR           : Message = "Erreur d'écriture ou de lecture dans le fichier."; break;
    case BZ_DATA_ERROR         : Message = "Problème d'intégrité des données lors de la décompression."; break;
    case BZ_UNEXPECTED_EOF     : Message = "La fin du fichier est inattendue, le fichier est peut-être corrompu."; break;
    case BZ_SEQUENCE_ERROR     : Message = "Le fichier ne peut être lu si celui-ci a été ouvert en mode écriture ou vice-versa."; break;
    case BZ_PARAM_ERROR        : Message = "Erreur de programmation.\nLe handle du fichier est NULL."; break;
    case BZ_DATA_ERROR_MAGIC   : Message = "Le fichier à décompresser n'est pas un fichier bz2."; break;
    case BZ_OUTBUFF_FULL       : Message = "Le buffer de sorti est plein."; break;
    case ERROR_LOAD_BZ2_LIB    : Message = "Impossible de charger la librairie de compression/décompression."; break;
    case ERROR_VERY_OLD_BZ2    : Message = "Impossible de trouver les fonctions de la librairie de compression.\nCela est sûrement dû a une librairie trop ancienne."; break;
    case ERROR_OPEN_BZ2_FILE   : Message = "Impossible d'ouvrir le fichier spécifié."; break;
  }
}
//---------------------------------------------------------------------------
void __fastcall ThrowExceptionIfBZ2Error( int nError)
{
  if( nError>=0) return;
  if( nError==BZ_MEM_ERROR) OutOfMemoryError();
                       else throw BZ2Exception( nError);
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Fonctions allocations de mémoire pour la lib BZ2
//---------------------------------------------------------------------------
void *CTarBZ2_alloc( void *opaque, int nItems, int nSize)
{
  return ( void*)::GlobalAlloc( GMEM_FIXED, (( DWORD)nItems)*(( DWORD)nSize));
}
//---------------------------------------------------------------------------
void CTarBZ2_free( void *opaque, void *p)
{
  ::GlobalFree( p);
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Gestion des flux avec compression/décompression au format BZ2
//---------------------------------------------------------------------------
typedef int __fastcall ( __closure *TReadWriteCallback)( LPVOID lpvBuffer, int nCount);
class TBZ2Stream : public TStream
{
  protected:
    enum BZ2_MODE { BM_UNKNOW, BM_INIT, BM_READING, BM_WRITING };

    #define IS_UNKNOW_MODE  ( m_eModeBZ2==BM_UNKNOW )
    #define IS_READING_MODE ( m_eModeBZ2==BM_READING)
    #define IS_WRITING_MODE ( m_eModeBZ2==BM_WRITING)

  private:
    // Prototype des fonctions de la lib BZ2
    // A vérifier ds bzlib.h en cas d'utilisation d'une nouvelle version de libbz2
    typedef int ( BZ_API( *TBZ2CompressInit  ))( bz_stream*, int, int, int);
    typedef int ( BZ_API( *TBZ2Compress      ))( bz_stream*, int);
    typedef int ( BZ_API( *TBZ2CompressEnd   ))( bz_stream*);
    typedef int ( BZ_API( *TBZ2DecompressInit))( bz_stream*, int, int);
    typedef int ( BZ_API( *TBZ2Decompress    ))( bz_stream*);
    typedef int ( BZ_API( *TBZ2DecompressEnd ))( bz_stream*);

    HINSTANCE          m_hinstBZ2DLL;      // Handle de la libbz2.dll
    TBZ2CompressInit   m_bzCompressInit;   // Pointeurs sur les fonctions de libbz2.dll
    TBZ2Compress       m_bzCompress;       //
    TBZ2CompressEnd    m_bzCompressEnd;    //
    TBZ2DecompressInit m_bzDecompressInit; //
    TBZ2Decompress     m_bzDecompress;     //
    TBZ2DecompressEnd  m_bzDecompressEnd;  //

    void __fastcall Init( TReadWriteCallback fnCallback);
    void __fastcall InitBZ2( BZ2_MODE eMode);
    void __fastcall Read2None( int nCount);

    int  __fastcall CompressAndWrite( int nAction);

  protected:
    BZ2_MODE  m_eModeBZ2;    // Mode de fonctionnement compression(écriture)/décompression(lecture)
    int       m_bzError;     // Numéro de la dernière erreur connue
    int       m_nReading;    // Nombre d'octets décompressés lus
    int       m_nBlockSize;  // Fixe le niveau de compression
    bool      m_bSmall;      // + ou - de mémoire pour la décompression
    bz_stream m_bzStream;
    BYTE     *m_btBuffer;
    TReadWriteCallback m_pfnCallback;

  public:
    // Fonction de lecture, d'écriture et de positionnement dans le flux BZ2
    virtual int __fastcall Read (       void *lpvBuffer, int nCount);
    virtual int __fastcall Write( const void *lpvBuffer, int nCount);
    virtual int __fastcall Seek( int nOffset, Word wOrigin);

  public:
            __fastcall  TBZ2Stream( TReadWriteCallback fnCallback);
            __fastcall  TBZ2Stream( TReadWriteCallback fnCallback, int nBlockSize);
            __fastcall  TBZ2Stream( TReadWriteCallback fnCallback, bool bSmall);
    virtual __fastcall ~TBZ2Stream( void);
};
//---------------------------------------------------------------------------
__fastcall TBZ2Stream::TBZ2Stream( TReadWriteCallback fnCallback)
{
  Init( fnCallback);
}
//---------------------------------------------------------------------------
__fastcall TBZ2Stream::TBZ2Stream( TReadWriteCallback fnCallback, int nBlockSize)
{
  Init( fnCallback); m_nBlockSize = nBlockSize;
}
//---------------------------------------------------------------------------
__fastcall TBZ2Stream::TBZ2Stream( TReadWriteCallback fnCallback, bool bSmall)
{
  Init( fnCallback); m_bSmall = bSmall;
}
//---------------------------------------------------------------------------
void __fastcall TBZ2Stream::Init( TReadWriteCallback fnCallback)
{
  ASSERT( fnCallback);

  m_hinstBZ2DLL      = NULL;
  m_btBuffer         = NULL;
  m_bzCompressInit   = NULL;
  m_bzCompress       = NULL;
  m_bzCompressEnd    = NULL;
  m_bzDecompressInit = NULL;
  m_bzDecompress     = NULL;
  m_bzDecompressEnd  = NULL;
  m_pfnCallback      = fnCallback;
  m_eModeBZ2         = BM_UNKNOW;
  m_nReading         = 0;
  m_nBlockSize       = 9;
  m_bSmall           = false;

  //Chargement de la librairie libbz2.dll
  m_hinstBZ2DLL  = ::LoadLibrary( LIBBZ2);
  if( m_hinstBZ2DLL==NULL) ThrowExceptionIfBZ2Error( ERROR_LOAD_BZ2_LIB);

  // Et récupération des fonctions exportées nécessaires
  m_bzCompressInit   = ( TBZ2CompressInit  )::GetProcAddress( m_hinstBZ2DLL, "BZ2_bzCompressInit"  );
  m_bzCompress       = ( TBZ2Compress      )::GetProcAddress( m_hinstBZ2DLL, "BZ2_bzCompress"      );
  m_bzCompressEnd    = ( TBZ2CompressEnd   )::GetProcAddress( m_hinstBZ2DLL, "BZ2_bzCompressEnd"   );
  m_bzDecompressInit = ( TBZ2DecompressInit)::GetProcAddress( m_hinstBZ2DLL, "BZ2_bzDecompressInit");
  m_bzDecompress     = ( TBZ2Decompress    )::GetProcAddress( m_hinstBZ2DLL, "BZ2_bzDecompress"    );
  m_bzDecompressEnd  = ( TBZ2DecompressEnd )::GetProcAddress( m_hinstBZ2DLL, "BZ2_bzDecompressEnd" );

  if( !m_bzCompressInit   || !m_bzCompress   || !m_bzCompressEnd ||
      !m_bzDecompressInit || !m_bzDecompress || !m_bzDecompressEnd) ThrowExceptionIfBZ2Error( ERROR_VERY_OLD_BZ2);
}
//---------------------------------------------------------------------------
__fastcall TBZ2Stream::~TBZ2Stream()
{
  // Libération de l'objet BZ2
  if( IS_WRITING_MODE) {
    while( CompressAndWrite( BZ_FINISH)!=BZ_STREAM_END);
    ThrowExceptionIfBZ2Error( m_bzCompressEnd( &m_bzStream));
  } else
    if( IS_READING_MODE) ThrowExceptionIfBZ2Error( m_bzDecompressEnd( &m_bzStream));

  m_eModeBZ2 = BM_UNKNOW;

  delete []m_btBuffer;

  // Déchargement de la lib
  ::FreeLibrary( m_hinstBZ2DLL);
                 m_hinstBZ2DLL = NULL;

  ThrowExceptionIfBZ2Error( m_bzError);
}
//---------------------------------------------------------------------------
void __fastcall TBZ2Stream::InitBZ2( BZ2_MODE eMode)
{
  ASSERT( m_eModeBZ2==BM_UNKNOW);

  // Initialisation de l'objet BZ2 en mode lecture ou écriture
  m_eModeBZ2 = BM_INIT;

  ::ZeroMemory( &m_bzStream, sizeof( bz_stream));
  m_bzStream.bzalloc = CTarBZ2_alloc;
  m_bzStream.bzfree  = CTarBZ2_free;
  m_btBuffer         = new BYTE[BZ_MAX_UNUSED];

  switch( eMode) {
    case BM_READING : m_nReading  = 0;
                      ThrowExceptionIfBZ2Error( m_bzDecompressInit( &m_bzStream, 0, ( int)m_bSmall));
                      break;
    case BM_WRITING : ThrowExceptionIfBZ2Error( m_bzCompressInit( &m_bzStream, m_nBlockSize, 0, 0));
                      break;
  }

  m_eModeBZ2 = eMode;
}
//---------------------------------------------------------------------------
int __fastcall TBZ2Stream::Read( void *lpvBuffer, int nCount)
{
  // Lecture de nCount octets dans le flux BZ2
  // L'appel de cette fonction provoque l'initialisation de l'objet BZ2 en mode lecture
  if( IS_UNKNOW_MODE) InitBZ2( BM_READING);

  int nRead = 0;

  while( nRead<nCount) {
    if( m_bzStream.avail_in<=0) {
      m_bzStream.avail_in = m_pfnCallback( m_btBuffer, BZ_MAX_UNUSED);
      m_bzStream.next_in  = m_btBuffer;
    }

    ASSERT( m_bzStream.avail_in>0);
    m_bzStream.next_out       = ( char*)lpvBuffer + nRead;
    m_bzStream.avail_out      = ( nCount - nRead);
    m_bzStream.total_out_lo32 = 0;
    m_bzStream.total_out_hi32 = 0;
    ThrowExceptionIfBZ2Error( m_bzDecompress( &m_bzStream));
    ASSERT(  m_bzStream.total_out_hi32==0);
    nRead += m_bzStream.total_out_lo32;
  }

  m_nReading += nRead;
  ASSERT( nRead==nCount);

  return nRead;
}
//---------------------------------------------------------------------------
void __fastcall TBZ2Stream::Read2None( int nCount)
{
  // Lit nCount octets dans le flux mais ceux-ci sont perdus.
  // Cette fonction est utilisée dans Seek pour se déplacer ds le flux par rapport à la position courante
  if( nCount==0) return;
  if( nCount< 0) ThrowExceptionIfBZ2Error( BZ_IO_ERROR);

  char szBuffer[512];
  for(; nCount>0; nCount-=512) Read( szBuffer, nCount>512 ? 512: nCount);
}
//---------------------------------------------------------------------------
int __fastcall TBZ2Stream::Seek( int nOffset, Word wOrigin)
{
  if( IS_UNKNOW_MODE) InitBZ2( BM_READING);

  // On ne peut pas déplacer la position par rapport au début du flux mais
  // seulement par rapport à la position courante et si se déplacement est positif (pas de retour en arrière possible).
  if(( wOrigin==soFromBeginning) && ( nOffset!=0)) {
    ASSERT( nOffset>=m_nReading);
    wOrigin = soFromCurrent;
    nOffset = nOffset - m_nReading;
  }

  switch( wOrigin) {
    case soFromBeginning : ASSERT(( nOffset==0) && ( m_nReading==0));
                           return 0;
    case soFromEnd	     : ASSERT( nOffset==0);
                           return 0x7FFFFFFF; // Impossible de connaitre la taille du fichier donc on donne la taille max possible
    case soFromCurrent	 : ASSERT( nOffset>=0);
                           Read2None( nOffset);
                           return m_nReading;
  }

  return 0;
}
//---------------------------------------------------------------------------
int __fastcall TBZ2Stream::CompressAndWrite( int nAction)
{
  int nReturn = 0;

  m_bzStream.next_out       = m_btBuffer;
  m_bzStream.avail_out      = BZ_MAX_UNUSED;
  m_bzStream.total_out_lo32 = 0;
  m_bzStream.total_out_hi32 = 0;
  nReturn                   = m_bzCompress( &m_bzStream, nAction);
  ThrowExceptionIfBZ2Error( nReturn);
  ASSERT( m_bzStream.total_out_hi32==0);
  if( m_pfnCallback( m_btBuffer, m_bzStream.total_out_lo32)!=m_bzStream.total_out_lo32)
    ThrowExceptionIfBZ2Error( BZ_IO_ERROR);

  return nReturn;
}
//---------------------------------------------------------------------------
int __fastcall TBZ2Stream::Write( const void *lpvBuffer, int nCount)
{
  // Ecriture de nCount octets à compresser
  // L'appel de cette fonction provoque l'initialisation de l'objet BZ2 en mode écriture
  if( IS_UNKNOW_MODE) InitBZ2( BM_WRITING);

  m_bzStream.next_in  = ( char*)lpvBuffer;
  m_bzStream.avail_in = nCount;

  while( m_bzStream.avail_in>0) CompressAndWrite( BZ_RUN);

  return nCount;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
AnsiString __fastcall IncludeTrailingBackslashIfNotEmpty( const AnsiString &strPath)
{
  return strPath.IsEmpty() ? EmptyStr : IncludeTrailingBackslash( strPath);
}
//---------------------------------------------------------------------------
AnsiString __fastcall GetUserTempPath()
{
  char szTempPath[MAX_PATH];
  if( ::GetTempPath( MAX_PATH, szTempPath)>0)
    return IncludeTrailingBackslash( szTempPath);
  return EmptyStr;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#define SCAN_DIRECTORY( dir_case, file_case) { \
  TSearchRec searchRec; \
  if( FindFirst( strDir + "*.*", faAnyFile, searchRec)==0) { \
    do { \
      if(( searchRec.Name==".") || ( searchRec.Name=="..")) continue; \
      if ( searchRec.Attr & faDirectory) { \
        AnsiString strNewDir( IncludeTrailingBackslash( strDir + searchRec.Name)); \
        if( AcceptDirectory( strNewDir)) { dir_case } \
      } else { \
        if( AcceptFile( strDir, searchRec.Name)) { file_case } \
      } \
    } while( FindNext( searchRec)==0); \
    FindClose( searchRec); \
  } \
}
//---------------------------------------------------------------------------
// Fait progresser le pourcentage du traitement
#define PROGRESS( inc_m_nProgress) { \
  if( m_evProgress) { \
    { inc_m_nProgress } \
    if(( 100*m_nProgress/m_nMaxProgress)>m_nPourcent) { \
      m_nPourcent = 100 * m_nProgress / m_nMaxProgress; \
      m_evProgress( this, m_nPourcent); \
    } \
  } \
}
//---------------------------------------------------------------------------
__fastcall CTarBZ2::CTarBZ2( TComponent *pOwner) : TComponent( pOwner)
{
  m_bz2Stream         = NULL;
  m_tarWriter         = NULL;
  m_fileStream        = NULL;
  m_evAcceptDirectory = NULL;
  m_evAcceptFile      = NULL;
  m_evAcceptExplode   = NULL;
  m_evProgress        = NULL;
  m_evStart           = NULL;
  m_evTerminate       = NULL;
  m_evArchiveExists   = NULL;
  m_szCache           = NULL;
  m_eStatus           = TBZ2_NONE;
  m_bSmall            = false;
  m_bRemovableDevice  = false;
  m_lstrUserDatas     = NULL;
  m_lstrUserDatas     = new TStringList();
  m_nBlockSize        =  9;
  m_nSupport          =  0;
  m_u64FreeSpace      =  0;
  m_nCache            =  0;
  m_nReadFile         = -1;
}
//---------------------------------------------------------------------------
__fastcall CTarBZ2::~CTarBZ2()
{
  delete m_lstrUserDatas;
         m_lstrUserDatas = NULL;
}
//---------------------------------------------------------------------------
int __fastcall CTarBZ2::ReadDatas( LPVOID lpvBuffer, int nCount)
{
  int nReading = 0;
  if(( m_nReadFile==0) && !InsertNewSupportAndOpen()) Abort();

  nReading     = m_fileStream->Read( lpvBuffer, nCount>m_nReadFile ? m_nReadFile : nCount);
  m_nReadFile -= nReading;

  return nReading;
}
//---------------------------------------------------------------------------
int __fastcall CTarBZ2::WriteDatas( LPVOID lpvBuffer, int nCount)
{
  if( !m_fileStream) return nCount;

  int nCacheDispo = CACHE_SIZE - m_nCache;
  if( nCount>nCacheDispo) {
    memcpy( m_szCache + m_nCache, lpvBuffer, nCacheDispo); m_nCache += nCacheDispo;
    FlushDatas( FLUSH_RUN);
    return nCacheDispo + WriteDatas(( char*)lpvBuffer + nCacheDispo, nCount-nCacheDispo);
  } else {
    memcpy( m_szCache + m_nCache, lpvBuffer, nCount); m_nCache += nCount;
  }

  return nCount;
}
//---------------------------------------------------------------------------
void __fastcall CTarBZ2::FlushDatas( Word wMode)
{
  if(( m_nCache==0) || !m_fileStream) return;

  if( m_nCache>m_u64FreeSpace) {
    int nFreeSpace = m_u64FreeSpace;
    // On va manquer de place pour écrire le buffer sur le support
    if( !m_bRemovableDevice) ThrowExceptionIfBZ2Error( BZ_IO_ERROR);
    if( m_fileStream->Write( m_szCache, nFreeSpace)!=nFreeSpace) ThrowExceptionIfBZ2Error( BZ_IO_ERROR);
    // Demande l'insertion d'un nouveau support
    if( !InsertNewSupportAndOpen()) Abort();
    // On remet le cache en état car seulement 1 seule partie a été enregistrée
    m_nCache -= nFreeSpace;
    memmove( m_szCache, m_szCache + nFreeSpace, m_nCache);
    // Si c'est la fin on force à enregistrer le reste du cache
    if( wMode==FLUSH_FINISH) FlushDatas( FLUSH_FINISH);
    return;
  }

  if( m_fileStream->Write( m_szCache, m_nCache)!=m_nCache) ThrowExceptionIfBZ2Error( BZ_IO_ERROR);
  m_u64FreeSpace -= m_nCache;
  m_nCache        = 0;
}
//---------------------------------------------------------------------------
bool __fastcall CTarBZ2::InsertNewSupportAndOpen()
{
  // Ferme le fichier en cours
  if( m_fileStream) { delete m_fileStream; m_fileStream = NULL; }
  // Support suivant
  m_nSupport++;
  ASSERT( m_nSupport>=1);
  // Demande d'insertion d'un nouveau support
  AnsiString strNewFilename;
  strNewFilename = m_nSupport==1 ? m_strFilename :
                   m_strFilename.SubString( 1, m_strFilename.Length()-ExtractFileExt( m_strFilename).Length()) + AnsiString().sprintf( ".%03d", m_nSupport);
  if( !InsertNewFloppy( strNewFilename)) return false;
  // Ouverture du nouveau fichier
  m_fileStream = new TFileStream( strNewFilename, m_bImplode ? fmCreate : fmOpenRead);
  if( !m_bImplode) m_nReadFile = m_fileStream->Size;
              else m_nReadFile = -1;
  return true;
}
//---------------------------------------------------------------------------
void __fastcall CTarBZ2::Implode( const AnsiString &strFilenameTBZ2, const AnsiString &strPath)
{
  char szCurrentPath[MAX_PATH];

  // Initialisation
  m_nCache           = 0;
  m_nSupport         = 0;
  m_bImplode         = true;
  m_strFilename      = strFilenameTBZ2;
  m_strStartDir      = IncludeTrailingBackslash( strPath);
  UINT uiDriveType   = ::GetDriveType( IncludeTrailingBackslash( ExtractFileDrive( strFilenameTBZ2)).c_str());
  m_bRemovableDevice = ( uiDriveType==DRIVE_REMOVABLE);
  if(( uiDriveType==DRIVE_UNKNOWN) || ( uiDriveType==DRIVE_NO_ROOT_DIR)) {
    ::EICErreur( "Le lecteur spécifié n'existe pas.");
    return;
  }

  // Sauvegarde le chemin de travail en cours et le modifie pour le faire pointer sur le répertoire à sauvegarder
  getcwd( szCurrentPath, MAX_PATH);
  chdir( strPath.c_str());

  try {
    if( !InsertNewSupportAndOpen()) return;
    m_szCache     = (char *)CTarBZ2_alloc(NULL,1,CACHE_SIZE);//new char[CACHE_SIZE];
    m_bz2Stream   = new TBZ2Stream( &WriteDatas, BlockSize);
    m_tarWriter   = new TTarWriter( m_bz2Stream);

    // Préparation de la compression
    InitImplodeExplode( TBZ2_IMPLODE);

    try {
      // S'il existe des données utilisateur, on les archive ds un fichier spécial
      if( UserDatas->Count) {
        AnsiString strTempPath( GetUserTempPath());
        AnsiString strTempFilename( strTempPath + USERDATAS_FILE);
        UserDatas->SaveToFile( strTempFilename);
        chdir( strTempPath.c_str());
        m_tarWriter->AddFile( USERDATAS_FILE, EmptyStr);
        chdir( strPath.c_str());
        DeleteFile( strTempFilename);
      }

      // Archivage et compression des fichiers
      AddFiles( EmptyStr);
    } catch( ...) {
      throw;
    }
  } __finally {
    // Retour à l'état initial
    chdir( szCurrentPath);
    delete m_tarWriter;
           m_tarWriter = NULL;
    delete m_bz2Stream;
           m_bz2Stream = NULL;
    FlushDatas( FLUSH_FINISH);
    delete m_fileStream;
           m_fileStream = NULL;
    CTarBZ2_free(NULL,m_szCache);//delete []m_szCache;
             m_szCache  = NULL;
  }

  Terminate();
}
//---------------------------------------------------------------------------
void __fastcall CTarBZ2::Explode( const AnsiString &strFilenameTBZ2, const AnsiString &strPath)
{
  TTarDirRec   DirRec;
  TTarArchive *tarArchive = NULL;
  UINT         unErrMode;
  bool         bArchExists;

  // Vérifie que l'archive existe (par exemple que l'utilisateur n'a pa retiré sa disquette)
  /*unErrMode   = ::SetErrorMode( SEM_FAILCRITICALERRORS); // Pour éviter d'avoir la fenêtre comme quoi le lecteur n'est pas prêt sous NT
  bArchExists = FileExists( strFilenameTBZ2);
  ::SetErrorMode( unErrMode);
  if( !bArchExists) ThrowExceptionIfBZ2Error( ERROR_OPEN_BZ2_FILE);*/

  // Initialisation
  m_nSupport         = 0;
  m_bImplode         = false;
  m_strFilename      = strFilenameTBZ2;
  m_strStartDir      = IncludeTrailingBackslash( strPath);
  m_bRemovableDevice = ( ::GetDriveType( IncludeTrailingBackslash( ExtractFileDrive( strFilenameTBZ2)).c_str())==DRIVE_REMOVABLE);

  try {
    if( !InsertNewSupportAndOpen()) Abort();

    m_bz2Stream   = new TBZ2Stream ( &ReadDatas, Small);
    tarArchive    = new TTarArchive( m_bz2Stream);

    // Préparation de la décompression
    InitImplodeExplode( TBZ2_EXPLODE);

    try {
      tarArchive->Reset();

      if( tarArchive->FindNext( DirRec)) {
        AnsiString strFilename( StringReplace( DirRec.Name, "/", "\\", TReplaceFlags() << rfReplaceAll));
        UserDatas->Clear();
        // Si le 1er fichier dans l'archive correspond au fichier spécial, celui-ci est
        // récupéré pour alimenter UserDatas et l'évènement OnAcceptExplode est appelé
        // pour savoir si l'on peut continuer la décompression
        if( strFilename==USERDATAS_FILE) {
          AnsiString strTempFilename( GetUserTempPath() + USERDATAS_FILE);
          tarArchive->ReadFile( strTempFilename);
          UserDatas->LoadFromFile( strTempFilename);
          DeleteFile( strTempFilename);
          if( !AcceptExplode()) goto lblAbort;
        } else
          goto lblNoUserDatas;
        // Restauration des fichiers contenus dans le tar.bz2
        while( tarArchive->FindNext( DirRec)) {
    lblNoUserDatas:
          AnsiString strFilename( StringReplace( DirRec.Name, "/", "\\", TReplaceFlags() << rfReplaceAll));
          AnsiString strFullFilename( m_strStartDir + strFilename);
          if( AcceptFile( IncludeTrailingBackslashIfNotEmpty( ExtractFilePath( strFilename)), ExtractFileName( strFilename))) {
            if( !DirectoryExists( ExtractFilePath( strFullFilename)))
                ForceDirectories( ExtractFilePath( strFullFilename));
            tarArchive->ReadFile( strFullFilename);
          }

          m_nPourcent = 0;
          m_nMaxProgress = m_fileStream->Size;
          PROGRESS( m_nProgress = m_fileStream->Position; )
        }
      }
lblAbort:
    } catch( ...) {
      throw;
    }
  } __finally {
    delete tarArchive;
           tarArchive  = NULL;
    delete m_bz2Stream;
           m_bz2Stream = NULL;
    delete m_fileStream;
           m_fileStream = NULL;
  }

  Terminate();
}
//---------------------------------------------------------------------------
void __fastcall CTarBZ2::AddFiles( const AnsiString &strDir)
{
  // Ajoute les fichiers désirés dans l'archive tar
  SCAN_DIRECTORY(
    // Cas d'un répertoire, on appelle récursivement la fonction AddFiles
    AddFiles( strNewDir); ,
    // Cas d'un fichier, ajout dans l'archive
    PROGRESS( m_nProgress++; )
    m_tarWriter->AddFile( strDir + searchRec.Name, EmptyStr);
  )
}
//---------------------------------------------------------------------------
int __fastcall CTarBZ2::CalculateMaxFiles( const AnsiString &strDir)
{
  // Calcule le nombre de fichier pour la progression en compression
  int nCount = 0;
  SCAN_DIRECTORY(
    // Cas d'un répertoire, on appelle récursivement la fonction CalculateMaxFiles
    nCount += CalculateMaxFiles( strNewDir); ,
    // Cas d'un fichier, on incrémente
    nCount++;
  )
  return nCount;
}
//---------------------------------------------------------------------------
void __fastcall CTarBZ2::Start()
{
  if( m_evStart) m_evStart( this);
}
//---------------------------------------------------------------------------
void __fastcall CTarBZ2::Terminate()
{
  if( m_evTerminate) m_evTerminate( this);
  m_eStatus = TBZ2_NONE;
}
//---------------------------------------------------------------------------
bool __fastcall CTarBZ2::AcceptDirectory( const AnsiString &strNewDir)
{
  bool bAccept = true;
  if( m_evAcceptDirectory)
      m_evAcceptDirectory( this, m_strStartDir, strNewDir, bAccept);
  return bAccept;
}
//---------------------------------------------------------------------------
bool __fastcall CTarBZ2::AcceptFile( const AnsiString &strDir, const AnsiString &strFilename)
{
  bool bAccept = true;
  if( m_evAcceptFile)
      m_evAcceptFile( this, m_strStartDir, strDir, strFilename, bAccept);
  return bAccept;
}
//---------------------------------------------------------------------------
bool __fastcall CTarBZ2::AcceptExplode()
{
  bool bAccept = true;
  if( m_evAcceptExplode)
      m_evAcceptExplode( this, bAccept);
  return bAccept;
}
//---------------------------------------------------------------------------
void __fastcall CTarBZ2::InitImplodeExplode( TARBZ2_STATUS eOperation)
{
  ASSERT(( eOperation==TBZ2_EXPLODE) || ( eOperation==TBZ2_IMPLODE));

  m_eStatus = TBZ2_INIT;
  Start();

  // Initialisation de la progression pour connaitre la valeur max
  m_nMaxProgress = m_nProgress = m_nPourcent = 0;
  if( m_evProgress) {
    if( eOperation==TBZ2_EXPLODE) {
      ASSERT( m_bz2Stream);
      m_nMaxProgress = m_fileStream->Size;
    } else
      m_nMaxProgress = CalculateMaxFiles( EmptyStr);
  }

  m_eStatus = eOperation;
}
//---------------------------------------------------------------------------
void __fastcall CTarBZ2::SetBlockSize( int nValue)
{
  m_nBlockSize = nValue<1 ? 1 : nValue>9 ? 9 : nValue;
}
//---------------------------------------------------------------------------
void __fastcall CTarBZ2::SetUserDatas( TStrings *lstrValue)
{
  UserDatas->Assign( lstrValue);
}
//---------------------------------------------------------------------------
bool __fastcall CTarBZ2::InsertNewFloppy( const AnsiString &strFilename)
{
  #define MIN_DISK_SIZE ( 100*1024)

  UINT             unErrMode;
  FILE            *file;
  AnsiString       strInsertDisk;
  AnsiString       strDrive( ExtractFileDrive( strFilename).UpperCase());
  AnsiString       strFilePath( IncludeTrailingBackslash( ExtractFilePath( strFilename)));
  AnsiString       strTestFile( strFilePath + "x.eic");
  TSearchRec       srInfos;
  bool             bForceMessage = false;

  if(( strDrive.Length()==2) && ( strDrive[2]==':')) strDrive = strDrive.SubString( 1, 1);

lblRetry3:
  strInsertDisk = ( m_bRemovableDevice && (( m_nSupport>1) || bForceMessage)) ? EICFormat( "Veuillez insérer le support n°%d dans le lecteur %s.", m_nSupport, strDrive.c_str()) : EmptyStr;

lblRetry:
  if( !strInsertDisk.IsEmpty() &&
    ( EICMsgBox( strInsertDisk, m_bImplode ? "Sauvegarde" : "Restauration", MB_ICONQUESTION | MB_OKCANCEL)==IDCANCEL)) return false;
  if( !m_bImplode) {
    if( !FileExists( strFilename)) {
      strInsertDisk = EICFormat( "Vous n'avez pas inséré le bon support dans le lecteur.\nVeuillez insérer le support n°%d dans le lecteur %s.", m_nSupport, strDrive.c_str());
      goto lblRetry;
    } else
      return true;
  }

lblRetry2:
  unErrMode = ::SetErrorMode( SEM_FAILCRITICALERRORS);
  int nResult = FindFirst( strFilePath + "*.*", faAnyFile, srInfos);
  bool bTestSupportOk = (( nResult==0) || ( nResult==2));
  FindClose( srInfos);
  ::SetErrorMode( unErrMode);
  if( !bTestSupportOk) { bForceMessage = true; goto lblRetry3; }

  if( !EICGetFreeSpace( ExtractFilePath( strFilename), m_u64FreeSpace)) return false;

  unErrMode = ::SetErrorMode( SEM_FAILCRITICALERRORS); // Pour éviter d'avoir la fenêtre comme quoi le lecteur n'est pas prêt sous NT
  file      = fopen( strTestFile.c_str(), "wb");
  ::SetErrorMode( unErrMode);
  if( file==NULL) {
    if( m_bRemovableDevice) {
      ::EICAttention( "Votre support est protégé en écriture.\nVeuillez le déprotéger avant de continuer l'opération.");
      goto lblRetry2;
    } else {
      ::EICAttention( "Impossible d'écrire sur le lecteur spécifié.");
      return false;
    }
  } else {
    fclose( file);
    DeleteFile( strTestFile);
  }

  if( FileExists( strFilename)) {
    bool bSupprFile = true;
    if( m_evArchiveExists) m_evArchiveExists( this, strFilename, bSupprFile);
                      else bSupprFile = ::EICQuestion( "Le fichier '" + strFilename + "' existe déjà sur le support.\nEtes-vous sûr de vouloir écraser ce fichier ?", "Confirmation écrasement");
    if( !bSupprFile) {
      if( !m_bRemovableDevice) return false;
      strInsertDisk = EICFormat( "Veuillez insérer un nouveau support dans le lecteur %s.", strDrive);
      goto lblRetry;
    } else {
      DeleteFile( strFilename);
      goto lblRetry2;
    }
  }

  if( m_u64FreeSpace<MIN_DISK_SIZE) {
    strInsertDisk = EICFormat( "Il n'y a pas assez de place disponible sur votre support.\nVeuillez insérer un nouveau support dans le lecteur %s.", strDrive);
    goto lblRetry;
  }

  return true;
}
//---------------------------------------------------------------------------
namespace Ctarbz2 {
	void __fastcall PACKAGE Register() {
		TComponentClass classes[1] = { __classid( CTarBZ2) };
		RegisterComponents( "EIC Outils", classes, 0);
  }
}
//---------------------------------------------------------------------------
